//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2011 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_ALGORITHM_CONTAINER_H_
#define _BUOLA_ALGORITHM_CONTAINER_H_

#include <buola/buola.h>
#include <buola/iterator/range.h>
#include <buola/traits/pointer.h>
#include <algorithm>

namespace buola {

///\addtogroup algorithm
///@{

///\name Container
//@{

///erases first occurence of a value in a container

///\param pCont the container
///\param pValue the value to be removed
///
///\return the number of elements erased
///
///The container must have an `erase` method which takes an iterator as a parameter
template<class tContainer,class tItem>
inline int erase_first_equal(tContainer &pCont,const tItem &pValue)
{
    auto lWhere=std::find(pCont.begin(),pCont.end(),pValue);

    if(lWhere!=pCont.end())
    {
        pCont.erase(lWhere);
        return 1;
    }
    return 0;
}

///erases first element in a container which matches a predicate

///\param pCont the container
///\param pPred the predicate
///
///\return the number of elements erased
///
///The container must have an `erase` method which takes an iterator as a parameter
template<class tContainer,class tPredicate>
inline int erase_first_if(tContainer &pCont,tPredicate &&pPred)
{
    auto lWhere=std::find_if(pCont.begin(),pCont.end(),std::forward<tPredicate>(pPred));

    if(lWhere!=pCont.end())
    {
        pCont.erase(lWhere);
        return 1;
    }
    return 0;
}

///erases all occurences of a value in a container

///\param pCont the container
///\param pValue the value to be removed
///
///The container must have an `erase` method which takes an iterator as a parameter
template<class tContainer,class tItem>
inline void erase_all_equal(tContainer &pCont,const tItem &pValue)
{
    pCont.erase(std::remove(pCont.begin(),pCont.end(),pValue),pCont.end());
}

///erases all elements in a container which match a predicate

///\param pCont the container
///\param pValue the value to be removed
///
///The container must have an `erase` method which takes an iterator as a parameter
template<class tContainer,class tPredicate>
inline void erase_all_if(tContainer &pCont,tPredicate &&pPred)
{
    pCont.erase(std::remove_if(pCont.begin(),pCont.end(),std::forward<tPredicate>(pPred)),pCont.end());
}

///erases all elements in an associative container which match a predicate

///\param pCont the container
///\param pValue the value to be removed
///
///The container must have an `erase` method which takes an iterator as a parameter
template<class tContainer,class tPredicate>
inline void associative_erase_if(tContainer &pCont,tPredicate &&pPred)
{
    for(auto i=pCont.begin();i!=pCont.end();)
    {
        if(pPred(*i))
            i=pCont.erase(i);
        else
            ++i;
    }
}

///constructs a new object and pushes it to the back of the provided container

///\param pCont the container where the object will be added
///\param pArgs... the arguments to pass to the constructor of the new object
///
///The container must hold (possibly smart) pointers to an object constructible with \p pArgs
///
///\return a reference to the newly constructed object
template<class tContainer,typename... tArgs>
typename MPointerToReference<typename tContainer::value_type>::type
inline construct_back(tContainer &pCont,tArgs&&... pArgs)
{
    typedef typename std::remove_cv<typename MRemovePointer<typename tContainer::value_type>::type>::type tBaseType;
    
    tBaseType *lObject=new tBaseType(std::forward<tArgs>(pArgs)...);
    
    pCont.emplace_back(lObject);
    
    return *lObject;
}

///constructs a new object and pushes it to the front of the provided container

///\param pCont the container where the object will be added
///\param pArgs... the arguments to pass to the constructor of the new object
///
///The container must hold (possibly smart) pointers to an object constructible with \p pArgs
///
///\return a reference to the newly constructed object
template<class tContainer,typename... tArgs>
typename MPointerToReference<typename tContainer::value_type>::type
inline construct_front(tContainer &pCont,tArgs&&... pArgs)
{
    typedef typename std::remove_cv<typename MRemovePointer<typename tContainer::value_type>::type>::type tBaseType;
    
    tBaseType *lObject=new tBaseType(std::forward<tArgs>(pArgs)...);
    
    pCont.emplace_front(lObject);
    
    return *lObject;
}

///constructs a new object and inserts it into the provided container

///\param pCont the container where the object should be inserted
///\param pPos iterator specifying where to insert the new object
///\param pArgs... the arguments to pass to the constructor of the new object
///
///The container must hold (possibly smart) pointers to an object constructible with \p pArgs
///
///\return a reference to the newly constructed object
template<class tContainer,typename... tArgs>
typename MPointerToReference<typename tContainer::value_type>::type
inline construct_insert(tContainer &pCont,typename tContainer::iterator pPos,tArgs&&... pArgs)
{
    typedef typename std::remove_cv<typename MRemovePointer<typename tContainer::value_type>::type>::type tBaseType;
    
    tBaseType *lObject=new tBaseType(std::forward<tArgs>(pArgs)...);
    
    pCont.emplace(pPos,lObject);
    
    return *lObject;
}

///resizes a pointer container, filling it with new objects if the size increases

///\param pCont container which will be resized
///\param pSize new size of the container
///\param pArgs... the arguments to pass to the constructor of the newly created objects
///
///The container must hold (possibly smart) pointers to an object constructible with \p pArgs
template<class tContainer,typename... tArgs>
inline void construct_resize(tContainer &pCont,std::size_t pSize,tArgs&&... pArgs)
{
    if(pSize<=pCont.size())
    {
        //using resize wouldn't work with value_types which are not CopyConstructible, even if resize would never
        //copy-construct an object
        pCont.erase(pCont.begin()+pSize,pCont.end());
    }
    else
    {
        pCont.reserve(pSize);

        pSize-=pCont.size();

        for(int i=0;i<pSize;i++)
            construct_back(pCont,std::forward<tArgs>(pArgs)...);
    }
}

//@}

///@}

/*namespace buola*/ }

#endif
